/*
 * RString.cpp
 *
 *  Created on: 29.08.2013.
 *      Author: Aco
 */

#include "RString.h"

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cassert>

int RString::nodeMin = 0;
int RString::nodeMax = 0;
std::vector<RString::RSNode> RString::nodeStore;
const char RString::NULL_CHAR = '\0';

uint32_t _RString_nextPOT(uint32_t n)
{
	if(n == 0) return 1;

	--n;
	n |= n >> 1;
	n |= n >> 2;
	n |= n >> 4;
	n |= n >> 8;
	n |= n >> 16;
	return n+1;
}

int RString::s_getNewNode()
{
	bool found = false;
	int id = 0;

	int tmpMax = (nodeMax < (int)nodeStore.size() - 1) ? nodeMax : (nodeStore.size() - 1);

	for(int i = nodeMin; i <= tmpMax; ++i)
	{
		if(nodeStore[i].refCount == 0)
		{
			found = true;
			id = i;
		}
	}

	if(!found)
	{
		nodeStore.push_back(RSNode());
		id = nodeStore.size() - 1;
	}

	if(id == (int)nodeMin)
		nodeMin++;

	nodeStore[id].refCount = 1;

	return id;
}

void RString::s_releaseNode(int id)
{
	if(id >= 0 && id < (int)nodeStore.size())
	{
		if(nodeStore[id].refCount <= 0) return;
		//assert(nodeStore[id].refCount > 0); //TODO reinforce assert

		nodeStore[id].refCount--;
		if(nodeStore[id].refCount <= 0)
		{
			nodeStore[id].refCount = 0;
			if(nodeStore[id].dataPtr != NULL)
			{
				free(nodeStore[id].dataPtr);
				nodeStore[id].dataPtr = NULL;
			}

			if(id < (int)nodeMin) nodeMin = id;
			if(id > (int)nodeMax) nodeMax = id;

			//printf("Release #%d - range : %d - %d\n", id, nodeMin, nodeMax);
		}
	}
}

void RString::s_lockNode(int id)
{
	if(id >= 0 && id < (int)nodeStore.size())
	{
		nodeStore[id].refCount++;
	}
}

int RString::s_compare(const uint16_t * a, const uint16_t * b)
{
	while(*a || *b)
	{
		int d = 0;
		d = (int)(*a) - (int)(*b);
		if(d != 0)
			return d;
		a++;
		b++;
	}
	return 0;
}

void RString::s_convert(const char * src, uint16_t * dst, int max)
{
	for(int i = 0; i < max; ++i)
	{
		dst[i] = src[i];
		if(dst[i] == 0)
			return;
	}
}

void RString::s_convert(const uint32_t * src, uint16_t * dst, int max)
{
	for(int i = 0; i < max; ++i)
	{
		dst[i] = src[i];
		if(dst[i] == 0)
			return;
	}
}

void RString::copyBack()
{
	int oldNode = storeID;
	storeID = s_getNewNode();

	nodeStore[storeID].dataPtr = (uint16_t*)malloc(chunkLength * sizeof(uint16_t));
	nodeStore[storeID].cSize = chunkLength * sizeof(uint16_t);
	if(data != NULL)
		memcpy(nodeStore[storeID].dataPtr, data, chunkLength * sizeof(uint16_t));
	data = nodeStore[storeID].dataPtr;

	s_releaseNode(oldNode);
}

void RString::prepForMod()
{
	if(storeID < 0)
	{
		//chunkLength = 32;
		//copyBack();
	}
	else
	{
		if(shared)
		{
			copyBack();
		}
		else
		{
			if(nodeStore[storeID].refCount > 1)
				copyBack();
		}
	}
}

/*
 *  Uint16 * data;
	int storeID;
	bool shared;
	Uint32 length;

	char * cdata;
	bool cdDirty;
 */

void RString::defInit()
{
	data = NULL;
	storeID = -1;
	shared = false;
	length = 0;
	chunkLength = 0;

	cdata = NULL;
	cdLength = 0;
	cdDirty = false;
}

RString::RString()
{
	defInit();
}

RString::RString(const RString & src)
{
	defInit();
	assign(src);
}

/*
RString::RString(const char * cstr)
{
	defInit();
	assignChars(cstr, 0);
}
*/

RString::RString(const std::string & src)
{
	defInit();
	assignChars(src.c_str(), 0);
}

RString::RString(const char * cstr, ...)
{
	defInit();

	char tmpBuff[1024];
	va_list argp;
	va_start(argp, cstr);
	vsprintf(tmpBuff, cstr, argp);
	va_end(argp);

	assignChars(tmpBuff, 0);
}

RString::RString(const uint16_t * wstr)
{
	defInit();
	assignChars(wstr, 0);
}

RString::RString(const uint32_t* wstr)
{
	defInit();
	assignChars(wstr, 0);
}

RString::~RString()
{
	if(storeID >= 0)
		s_releaseNode(storeID);
	if(cdata != NULL)
		free(cdata);
}

void RString::swapEndianness()
{
	if(length == 0)
		return;
	if(data == NULL)
		return;

	for(int i = 0; i < chunkLength; ++i)
	{
		uint8_t lower = data[i] & 0xFF;
		data[i] >>= 8;
		data[i] = data[i] | (lower << 8);
	}
}

void RString::clear()
{
	if(length == 0)
		return;
	if(data == NULL)
		return;

	prepStorage(1, false);
	data[0] = '\0';
	length = 0;
	cdDirty = true;
}

void RString::shiftChars(int pos, int dir)
{
	if(dir == 0)
		return;

	if(pos < 0)
		return;

	if(dir < 0 && pos + dir < 0)
		return;

	//if(dir > 0)
	//	prepStorage(length + pos + 1, true);
	//else
	//	prepForMod();
	prepStorage(length + dir + 1, true);
	prepForMod();

	if(dir > 0)
	{
		for(int i = length + dir - 1; i >= pos + dir; i--)
			data[i] = data[i - dir];
	}
	else
	{
		for(int i = pos + dir; i < length + dir; i++)
			data[i] = data[i - dir];
	}

	length += dir;
	data[length] = 0;
	cdDirty = true;
}

void RString::insertCharAt(uint16_t c, int pos)
{
	if(pos < 0 || pos > length)
		return;

	//if(pos == length)
	//{
	//	prepStorage(length + 1, true);
	//	prepForMod();
	//	data[pos] = c;
	//	data[pos + 1] = 0;
	//}
	//else
	{
		prepForMod();
		shiftChars(pos, 1);
		data[pos] = c;
	}
	cdDirty = true;
}

void RString::insertStringAt(const RString & src, int pos)
{
	if(pos < 0 || pos > length)
		return;

	shiftChars(pos, src.getLength());
	memcpy(data + pos, src.data, src.getLength() * sizeof(uint16_t));
	cdDirty = true;
}

void RString::setCharAt(uint16_t c, int pos)
{
	if(pos < 0 || pos > length)
		return;

	if(pos == length)
		prepStorage(length + 2, true);
	else
		prepForMod();

	data[pos] = c;
	if(pos == length)
	{
		length++;
		data[pos + 1] = 0;
	}
	cdDirty = true;
}

void RString::deleteRange(int start, int end)
{
	shiftChars(end, start - end);
	cdDirty = true;
}

void RString::assignChars(const char * src, int origin)
{
	if(src == NULL)
	{
		clear();
		return;
	}

	int srcLen = 0;
	while(src[srcLen++]);
	srcLen--;

	if(srcLen <= 0) return;

	prepStorage(origin + srcLen + 1, origin > 0);
	for(int i = 0; i <= srcLen; ++i)
		data[i + origin] = src[i];

	length = origin + srcLen;
	data[length] = 0;
	cdDirty = true;

	checkLength();
}

void RString::assignChars(const uint16_t * src, int origin)
{
	if(src == NULL)
	{
		clear();
		return;
	}

	int srcLen = 0;
	while(src[srcLen++]);
	srcLen--;

	if(srcLen <= 0 && origin == 0)
	{
		clear();
		return;
	}

	if(srcLen <= 0)
		return;

	prepStorage(origin + srcLen + 1, origin > 0);
	for(int i = 0; i <= srcLen; ++i)
		data[i + origin] = src[i];

	length = origin + srcLen;
	data[length] = 0;
	cdDirty = true;

	checkLength();
}

void RString::assignChars(const uint32_t * src, int origin)
{
	if(src == NULL)
	{
		clear();
		return;
	}

	int srcLen = 0;
	while(src[srcLen++]);
	srcLen--;

	if(srcLen <= 0) return;

	prepStorage(origin + srcLen + 1, origin > 0);
	for(int i = 0; i <= srcLen; ++i)
		data[i + origin] = src[i];

	length = origin + srcLen;
	data[length] = 0;
	cdDirty = true;

	checkLength();
}

void RString::prepStorage(int l, bool retain)
{
	prepForMod();

	if(storeID < 0)
		storeID = s_getNewNode();

	if(l > chunkLength)
	{
		int oldLength = chunkLength;
		chunkLength = _RString_nextPOT(l);

		assert(storeID >= 0);
		assert(nodeStore[storeID].refCount == 1);

		uint16_t * oldData = data;
		data = (uint16_t*)malloc(chunkLength * sizeof(uint16_t));
		nodeStore[storeID].dataPtr = data;
		nodeStore[storeID].cSize = chunkLength * sizeof(uint16_t);

		if(oldData != NULL)
		{
			if(retain)
				memcpy(data, oldData, oldLength * sizeof(uint16_t));
			free(oldData);
		}
		else
		{
			data[0] = 0;
		}
	}
}

void RString::checkLength()
{
#ifndef NDEBUG
	if(data == NULL)
	{
		assert(length == 0);
	}
	else
	{
		assert(length > 0);
		for(int i = 0; i < length; ++i)
			assert(data[i] != 0);
		assert(data[length] == 0);
	}
#endif
}

void RString::assign(const RString & src)
{
	if(storeID >= 0)
	{
		s_releaseNode(storeID);
		storeID = -1;
	}

	if(cdata != NULL)
	{
		free(cdata);
		cdata = NULL;
	}

	data = src.data;
	storeID = src.storeID;
	shared = true;
	length = src.length;
	chunkLength = src.chunkLength;
	s_lockNode(storeID);

	//if(cdata != NULL) free(cdata);
	//cdata = NULL;
	cdLength = 0;
	cdDirty = true;

	checkLength();
}

RString RString::substring(int start, int end) const
{
	RString sub;
	sub.prepStorage(end - start + 1, false);
	memcpy(sub.data, data + start, (end - start) * sizeof(uint16_t));
	sub.data[end - start] = 0;
	sub.length = end - start;
	sub.cdDirty = true;
	sub.checkLength();
	return sub;
}

uint32_t RString::getLength() const
{
	return length;
}

const uint16_t * RString::getWData() const
{
	return data;
}

const char * RString::c_str() const
{
	if(length == 0)
		return &NULL_CHAR;

	if(cdDirty)
	{
		RString * cthis = const_cast<RString*>(this);

		if(cthis->cdata == NULL)
		{
			cthis->cdata = (char*)malloc(length + 1);
			cthis->cdLength = length + 1;
		}
		else if(cdLength < (int)length + 1)
		{
			free(cdata);
			cthis->cdata = (char*)malloc(length + 1);
			cthis->cdLength = (int)length + 1;
		}

		for(int i = 0; i <= (int)length; ++i)
			cthis->cdata[i] = data[i] & 0xFF;

		cthis->cdDirty = false;
	}

	return cdata;
}

void RString::setF(const char * fmt, ...)
{
	char tmpBuff[1024];
	va_list argp;
	va_start(argp, fmt);
	vsprintf(tmpBuff, fmt, argp);
	va_end(argp);
	assignChars(tmpBuff, 0);
}

void RString::appendF(const char * fmt, ...)
{
	char tmpBuff[1024];
	va_list argp;
	va_start(argp, fmt);
	vsprintf(tmpBuff, fmt, argp);
	va_end(argp);
	assignChars(tmpBuff, length);
}

int RString::compareTo(const RString & src) const
{
	if(src.length == 0 && length == 0)
		return 0;
	return s_compare(data, src.data);
}

int RString::countChars(uint16_t c) const
{
	if(length == 0) return 0;
	int count = 0;
	for(int i = 0; i < length; ++i)
		count += data[i] == c;
	return count;
}

bool RString::toInt(int & out) const
{
	return sscanf(c_str(), "%d", &out) > 0;
}

bool RString::toFloat(float & out) const
{
	return sscanf(c_str(), "%f", &out) > 0;
}

bool RString::isEmpty() const
{
	return length == 0;
}

bool RString::notEmpty() const
{
	return length > 0;
}

RString & RString::operator= (const RString &src)
{
	assign(src);
	return *this;
}

RString & RString::operator= (const char * src)
{
	assignChars(src, 0);
	return *this;
}

RString & RString::operator= (const uint16_t * src)
{
	assignChars(src, 0);
	return *this;
}

RString & RString::operator= (const uint32_t * src)
{
	assignChars(src, 0);
	return *this;
}

RString & RString::operator+= (const RString &src)
{
	if(data != NULL)
		assert(data[length] == 0);

	if(src.data != NULL)
		assert(src.data[src.length] == 0);

	assignChars(src.data, length);
	return *this;
}

RString & RString::operator+= (const char * src)
{
	assignChars(src, length);
	return *this;
}

RString & RString::operator+= (const uint16_t * src)
{
	assignChars(src, length);
	return *this;
}

RString & RString::operator+= (const uint32_t * src)
{
	assignChars(src, length);
	return *this;
}

RString & RString::operator+= (uint16_t c)
{
	prepStorage(length + 2, true);

	assert(data != NULL);
	assert(data[length] == 0);

	data[length] = c;
	data[length + 1] = 0;
	length++;

	checkLength();

	return *this;
}

RString RString::operator+ (const RString &src) const
{
	RString res(*this);
	res += src;
	return res;
}

RString RString::operator+ (const char * src) const
{
	RString res(*this);
	res += src;
	return res;
}

bool RString::operator== (const RString &src) const
{
	if(data == NULL && src.data == NULL) return true;
	if(data == NULL || src.data == NULL) return false;

	return s_compare(data, src.data) == 0;
}

bool RString::operator== (const char * src) const
{
	if(data == NULL && src == NULL) return true;
	if(data == NULL || src == NULL) return false;

	uint16_t buff[1024];
	s_convert(src, buff, 1024);
	return s_compare(data, buff) == 0;
}

bool RString::operator== (const uint16_t * src) const
{
	if(data == NULL && src == NULL) return true;
	if(data == NULL || src == NULL) return false;

	return s_compare(data, src) == 0;
}

bool RString::operator== (const uint32_t * src) const
{
	if(data == NULL && src == NULL) return true;
	if(data == NULL || src == NULL) return false;

	uint16_t buff[1024];
	s_convert(src, buff, 1024);
	return s_compare(data, buff) == 0;
}

void RString::dumpStats()
{
	printf("free range %d - %d\n", nodeMin, nodeMax);
	for(unsigned int i = 0; i < nodeStore.size(); ++i)
		printf("#%u bytes=%d refs=%d\n", i, nodeStore[i].cSize, nodeStore[i].refCount);
}

char RString::charAt(int i) const
{
	if(cdata == NULL)
		return 0;
	else if(i >= 0 && i < length)
		return cdata[i];
	else
		return 0;
}

uint16_t RString::utfCharAt(int i) const
{
	if(data == NULL)
		return 0;
	else if(i >= 0 && i < length)
		return data[i];
	else
		return 0;
}

bool RString::startsWith(const char * prefix) const
{
	if(prefix == NULL) return false;
	return strncmp(c_str(), prefix, strlen(prefix)) == 0;
}

bool RString::endsWith(const char * suffix) const
{
	if(suffix == NULL) return false;
	int l = strlen(suffix);
	if(l > length)
		return false;
	return strncmp(c_str() + (length - l), suffix, l) == 0;
}
